package cn.edu.hbpu.filter;

import cn.edu.hbpu.dto.LoginUser;
import cn.edu.hbpu.handler.CustomException;
import cn.edu.hbpu.utils.JwtUtils;
import cn.edu.hbpu.utils.RedisCache;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

import static cn.edu.hbpu.common.Constants.FILTER_WHITE_NAME;


/**
 * @author lcy
 * @date 2022/8/5 18:00
 * @description
 */
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    /**
     * 校验token是否合法以及redis中token是否过期
     *
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 重点：放行filterChain.doFilter(request,  response);语句后面一定要接上return

        // 放行 处理 过滤器异常的请求 handleFilterException
        for (String filterWhiteName : FILTER_WHITE_NAME) {
            if ((filterWhiteName.equals(request.getRequestURI()))) {
                filterChain.doFilter(request, response);
                // 一定要return，不然走完过滤器链后，往回走回到这里后。会接着往下执行
                // 走到抛出异常那里后，又会抛出异常，又被ExceptionFilter捕捉到，又发起请求转发，经过该过滤器。无限循环下去。
                // 好在OncePerRequestFilter只会过滤一次。但是第二次过滤时的异常就会抛出，造成返回结果错误
                return;
            }
        }

        // 1. 从headers中获取token令牌
        String token = request.getHeader("Authorization");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        // 2. 解析token令牌获得userId
        String userId;
        try {
            Claims claims = JwtUtils.parseJwt(token);
            userId = claims.getSubject();
        } catch (ExpiredJwtException e) {
            throw new CustomException("token已过期");
        } catch (Exception e) {
            throw new CustomException("非法token");
        }

        // 3. 根据userId从redis中查询用户信息
        String redisKey = "user:loginedUser:" + userId;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        // 说明用户数据已经过期
        if (Objects.isNull(loginUser)) {
            throw new CustomException("登录已失效，请重新登录");
        }

        // 4. 将认证通过的用户信息存入 SecurityContextHolder中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        // 放行
        filterChain.doFilter(request, response);
    }
}
